<?php

namespace Hilaoyu\LaravelExtend\Services\AccessControl\Traits;

use Hilaoyu\Utils\UtilSupport;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;

trait ModelAuthUserWithAccessControlTrait
{

    abstract static protected function classNameModelAccessControlUsersRoles();

    abstract static protected function classNameModelAccessControlUsersRolesRelation();

    abstract static protected function classNameModelAccessControlSuperusers();

    public function eventOnConstructModelAuthUserWithAccessControlTrait(){
        $this->append(['is_super_user']);
    }

    public function getIsSuperUserAttribute()
    {
        return $this->accessControlIsSuperuser();
    }

    public function roles_relation()
    {
        $class_name_model_access_control_users_roles_relation = static::classNameModelAccessControlUsersRolesRelation();
        if (!$class_name_model_access_control_users_roles_relation) {
            return $this->newQuery()->whereRaw('1=0');
        }
        return $this->hasMany(
            $class_name_model_access_control_users_roles_relation,
            'user_id',
            'id'
        );
    }

    public function roles()
    {
        $class_name_model_access_control_users_roles_relation = static::classNameModelAccessControlUsersRolesRelation();
        $class_name_model_access_control_users_roles = static::classNameModelAccessControlUsersRoles();
        if (!$class_name_model_access_control_users_roles_relation || !$class_name_model_access_control_users_roles) {
            return $this->hasOne(
                get_class($this),
                $this->getKeyName()
            )->whereRaw('1=0');
        }
        return $this->hasManyThrough(
            $class_name_model_access_control_users_roles,
            $class_name_model_access_control_users_roles_relation,
            'user_id',
            'id',
            'id',
            'role_id'
        );
    }

    public function saveRoles($role_ids = [])
    {
        $role_ids = (array)$role_ids;
        $class_name_model_access_control_users_roles_relation = static::classNameModelAccessControlUsersRolesRelation();
        $class_name_model_access_control_users_roles = static::classNameModelAccessControlUsersRoles();
        if (!$class_name_model_access_control_users_roles_relation || !$class_name_model_access_control_users_roles) {
            return false;
        }
        try {
            $this->roles_relation()->delete();
            $roles_model = UtilSupport::makeClass($class_name_model_access_control_users_roles);
            $role_ids = $roles_model
                ->where('status', '=', '1')
                ->whereIn($roles_model->getKeyName(), $role_ids)
                ->get()
                ->map(function ($item) {
                    return ['role_id' => $item->getKey()];
                });


            if (!$role_ids->isEmpty()) {
                $this->roles_relation()->createMany($role_ids->toArray());
            }

            return true;

        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return false;
    }

    public function clearOwnedAccessesCache()
    {
        if ($_this_key = $this->getKey()) {

            return Cache::forget('auth_user_owned_accesses_' . $_this_key);
        }
        return false;
    }


    public function ownedAccesses($entity_id = '', $action = '', $default = null)
    {
        $entity_id = trim($entity_id);
        $action = trim($action);


        $accesses = [];

        if ($this->exists && ($_this_key = $this->getKey())) {
            $accesses = Cache::rememberForever('auth_user_owned_accesses_' . $_this_key, function () use ($_this_key,$action,$entity_id) {
                $roles = $this->roles()->where('status', '=', '1')->get();
                $user_accesses = [];
                $roles->each(function ($role) use (&$user_accesses) {
                    //$role->clearOwnedAccessesCache();
                    collect($role->ownedAccesses())->each(function ($actions, $e_id) use (&$user_accesses) {
                        collect($actions)->each(function ($val, $action) use (&$user_accesses, $e_id) {
                            $action_key = $e_id . '.' . $action;
                            $old_val = intval(data_get($user_accesses, $action_key, 0));
                            $new_val = 0;
                            switch ($action) {
                                case 'limit':
                                    if(-1 == $old_val || -1 == $val){
                                        $new_val = -1;
                                    }else{
                                        $new_val = max($val, $old_val);
                                    }

                                    break;
                                case 'boolean':
                                default:
                                    $new_val = ($val + $old_val) >= 1 ? 1 : 0;
                                    break;
                            }

                            data_set($user_accesses, $action_key, $new_val);
                        });
                    });
                });
                return $user_accesses;
            });
        }

        if ($entity_id) {
            $access_key = $entity_id;

            if ($action) {
                if ($this->accessControlIsSuperuser()) {
                    return true;
                }
                $access_key .= '.' . $action;
            }
            /*if('access_control' == $entity_id){
                dump($this->getAuthIdentifier(),$accesses,$access_key);
            }*/
            return data_get($accesses, $access_key, $default);
        }

        return $accesses;
    }

    public function superuser()
    {
        $class_name_model_access_control_superusers = static::classNameModelAccessControlSuperusers();
        if (!$class_name_model_access_control_superusers) {
            return $this->hasOne(
                get_class($this),
                $this->getKeyName()
            )->whereRaw('1=0');
        }
        return $this->hasOne(
            $class_name_model_access_control_superusers,
            'user_id'
        );
    }

    public function accessControlIsSuperuser()
    {
        if (!$this->exists) {
            return false;
        }
        $class_name_model_access_control_superusers = static::classNameModelAccessControlSuperusers();
        if (!$class_name_model_access_control_superusers) {
            return false;
        }

        return $this->superuser;
    }


    public function saveSuperusers()
    {
        $class_name_model_access_control_superusers = static::classNameModelAccessControlSuperusers();

        if (!$class_name_model_access_control_superusers) {
            return false;
        }
        try {
            $this->superuser()->delete();

            return $this->superuser()->create();

        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return false;
    }

    public function deleteSuperusers()
    {
        $class_name_model_access_control_superusers = static::classNameModelAccessControlSuperusers();

        if (!$class_name_model_access_control_superusers) {
            return false;
        }
        try {
            $this->superuser()->delete();

            return true;

        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return false;
    }


    public function checkAccess($identifier, $action)
    {

        $model = $this->checkIdentifierIfModel($identifier);
        return $this->checkAccessDo($identifier, $action, $model);

    }

    protected function checkAccessDo($identifier, $action, $model = null)
    {
        if(!$this->exists){
            return false;
        }
        $identifier = trim((string)$identifier);
        if (empty($identifier)) {
            return true;
        }

        //dump($identifier,$action,$model);



        if (in_array($action, ['create', 'allow'])) {
            return $this->ownedAccesses($identifier, $action, false);;
        }

        $user_auth_id = UtilSupport::tryQuiet(function () {
            return $this->getAuthIdentifier();
        });

        if (in_array($action, ['limit'])) {
            $ownedLimit = $this->ownedAccesses($identifier, $action, false);
            if (-1 == $ownedLimit || !$model) {
                return true;
            }

            $hasCount = UtilSupport::tryQuiet(function () use ($model) {
                return $model->accessControlLimitHasCount($this);
            });

            return $hasCount < $ownedLimit;
        }

        @list($action, $scope) = explode('_', $action);
        $model_with_owner = false;
        $model_owner_id = false;

        if ($model) {
            $model_with_owner = UtilSupport::tryQuiet(function () use ($model) {
                return '' != $model->accessGetOwnerFieldName();
            });
            $model_owner_id = UtilSupport::tryQuiet(function () use ($model) {
                return $model->accessGetOwnerId();
            });
        }

        if (!empty($scope)) {
            if(!$this->ownedAccesses($identifier, $action . '_' . $scope, false)){
                return false;
            }
            if ('all' == $scope) {
                return true;
            } elseif ($scope == 'group') {
                //dd($check);
                if (!$model || !$model_with_owner) {
                    return true;
                }

                if ($model_owner_id === $user_auth_id) {
                    return true;
                }

                return (bool)UtilSupport::tryQuiet(function () use ($model_owner_id) {
                    $this->getSameRoleModels()->firstWhere('id', '=', $model_owner_id);
                });
            } elseif ($scope == 'own') {

                if (!$model || !$model_with_owner) {
                    return true;
                }
                return $model_owner_id === $user_auth_id;
            }

            return false;
        }


        foreach (['all', 'group', 'own'] as $scope) {
            if ($this->checkAccessDo($identifier, $action . '_' . $scope, $model)) {
                return true;
            }
        }

        return false;
    }

    protected function checkIdentifierIfModel(&$identifier)
    {
        $model = null;
        if ($identifier instanceof Model) {
            $model = $identifier;
        } elseif (is_subclass_of($identifier, Model::class)) {
            $model = UtilSupport::tryQuiet(function () use ($identifier) {
                return UtilSupport::makeClassSingle($identifier);
            });
        }

        if ($model
            && $identifier_temp = UtilSupport::tryQuiet(function () use ($model) {
                return $model->accessGetAccessIdentifier();
            })
        ) {
            $identifier = $identifier_temp;
        }

        $identifier = trim((string)$identifier);

        return $model;
    }

    public function getSameRoleModels($id = '')
    {

        return $this->newModelQuery()->withSameRolesFilter($id)->get();

    }

    public function scopeWithSameRolesFilter($query, $id = '')
    {

        if ($id) {
            $model = static::makeModel($id);
        } else {
            $model = $this;
        }

        if(!$model){
            return $query->whereRaw('1=0');
        }

        $role_ids = $model->roles_relation()->pluck('role_id');

        $query = $query->whereHas('role_relation', function ($q) use ($role_ids) {
            return $q->whereIn('role_id', $role_ids);
        });
        return $query;
    }
}
